package departmentservice

import (
	"errors"
	"fmt"
	"strings"
	"time"
	"yunj/pkg/global"
	"yunj/pkg/yunj/core/builder"
	"yunj/pkg/yunj/core/builder/builderconsts"
	"yunj/pkg/yunj/core/builder/builderdto"
	"yunj/pkg/yunj/core/builder/builderenum"
	"yunj/pkg/yunj/core/builder/form"
	"yunj/pkg/yunj/core/builder/form/button"
	"yunj/pkg/yunj/core/builder/form/field"
	"yunj/pkg/yunj/core/builder/table"
	"yunj/pkg/yunj/core/builder/table/col"
	"yunj/pkg/yunj/core/builder/table/col/action"
	"yunj/pkg/yunj/core/builder/table/deftoolbar"
	"yunj/pkg/yunj/core/builder/table/toolbar"
	errcodeEnum "yunj/pkg/yunj/core/enum/errcode"
	"yunj/pkg/yunj/util"

	"yunj/pkg/yunj/app/data"
	"yunj/pkg/yunj/app/dto"
	"yunj/pkg/yunj/app/enum/admin/departmentenum"
	"yunj/pkg/yunj/app/enum/stateenum"
	"yunj/pkg/yunj/app/model"
	"yunj/pkg/yunj/app/validator/admin/departmentvalidator"

	"github.com/gin-gonic/gin"
	"github.com/worklz/go-validate"
	"gorm.io/gorm"
)

type departmentPage struct {
}

var DepartmentPage *departmentPage

// 列表是否为树形
func (s *departmentPage) listBuilderIsTree(stateCode any) bool {
	// 回收站开启分页、不使用树形展示
	return util.Float64ToUints(stateCode) != stateenum.RECYLE_BIN
}

// 列表构建器
func (s *departmentPage) ListBuilder(ctx *gin.Context) *builder.Table {
	return builder.NewTable(ctx, "DepartmentList").
		State([]builderdto.TableState{
			{Code: stateenum.NORMAL, Title: "正常"},
			{Code: stateenum.RECYLE_BIN, Title: "回收站"},
		}).
		Page(false).
		Tree(func(t *builder.Table, state *builderdto.TableState) (res bool, err error) {
			res = s.listBuilderIsTree(state.Code)
			return
		}).
		Filter(field.NewText("keywords", "关键词").SetPlaceholder("模糊匹配，名称/KEY")).
		Toolbar(func(t *builder.Table, state *builderdto.TableState) (actions []toolbar.ActionInterface, err error) {
			actions = []toolbar.ActionInterface{
				toolbar.NewOpenPopup("add", "添加").SetClass("layui-icon-add-circle").
					SetUrl(util.BuildAdminUrl("/department/list/add")).SetAuth("yunj_department_list_add"),
			}
			if state.Code == stateenum.RECYLE_BIN {
				actions = append(actions,
					toolbar.NewAsyncEvent(builderenum.TABLE_EVENT_TYPE_NORMAL, "恢复正常").SetDropdown(true).SetAuth("yunj_department_list_normal"),
					toolbar.NewAsyncEvent(builderenum.TABLE_EVENT_TYPE_DELETED, "永久删除").SetDropdown(true).SetAuth("yunj_department_list_deleted"),
				)
			} else {
				actions = append(actions,
					toolbar.NewOpenPopup(builderenum.TABLE_EVENT_TYPE_SORT, "排序").SetClass("yunj-icon-sort").
						SetUrl(util.BuildAdminUrl("/department/list/sort")).SetAuth("yunj_department_list_sort"),
					toolbar.NewAsyncEvent(builderenum.TABLE_EVENT_TYPE_RECYLE_BIN, "移入回收站").SetDropdown(true).SetAuth("yunj_department_list_recyle_bin"),
				)
			}
			return
		}).
		DefaultToolbar([]deftoolbar.ActionInterface{
			deftoolbar.NewFilter(),
			deftoolbar.NewExport().SetAuth("yunj_department_list_export"),
			deftoolbar.NewAsyncEvent("clearCache").SetTitle("清理缓存").SetClass("yunj-icon-clear").SetAuth("yunj_department_list_clear_cache"),
		}).
		Cols(func(t *builder.Table, state *builderdto.TableState) (cols []table.ColInterface, err error) {
			postionEnumOptions, err := model.GetNormalPostionTableColOptions()
			if err != nil {
				return
			}
			cols = []table.ColInterface{
				col.NewCheckbox("id", "ID"),
				col.NewText("name", "名称"),
				col.NewText("code", "标识"),
				col.NewEnum("type", "类型").SetOptions(builder.GetEnumTableColOptions(departmentenum.TypeEnum)).SetMaxWidth(80),
				col.NewText("intro", "简介"),
				col.NewTrigger("member_count", "成员数").SetAlign(builderconsts.ALIGN_CENTER).SetMaxWidth(80).
					SetEventType(builderconsts.EVENT_OPEN_TAB).SetEventTitle("成员管理").SetEventUrl(fmt.Sprintf("`%s?department_ids=`+ d.id", util.BuildAdminUrl("/member/list"))),
				col.NewEnum("postion_ids", "岗位").SetOptions(postionEnumOptions),
			}
			actionCol := col.NewAction("操作").SetOptions([]action.ActionInterface{
				action.NewOpenPopup("edit", "详情").SetClass("layui-icon-survey").SetUrl(util.BuildAdminUrl("/department/list/edit")).SetAuth("yunj_department_list_edit"),
			})
			if state.Code == stateenum.RECYLE_BIN {
				actionCol.Options = append(actionCol.Options,
					action.NewAsyncEvent(builderenum.TABLE_EVENT_TYPE_NORMAL, "恢复正常").SetDropdown(true).SetAuth("yunj_department_list_normal"),
					action.NewAsyncEvent(builderenum.TABLE_EVENT_TYPE_DELETED, "永久删除").SetDropdown(true).SetAuth("yunj_department_list_deleted").SetConfirmFormFields(field.NewTxt("tips", "确认删除？")),
				)
			} else {
				actionCol.Options = append(actionCol.Options, action.NewAsyncEvent(builderenum.TABLE_EVENT_TYPE_RECYLE_BIN, "移入回收站").SetDropdown(true).SetAuth("yunj_department_list_recyle_bin"))
				actionCol.Options = append([]action.ActionInterface{
					action.NewOpenPopup("itemAdd", "添加部门").SetClass("layui-icon-add-circle").SetUrl(util.BuildAdminUrl("/department/list/add", map[string]any{"isAddChild": "yes"})).SetAuth("yunj_department_list_add"),
					action.NewOpenTab("postionList", "`岗位管理(${d.postion_ids.length})`").SetClass("layui-icon-list").SetUrl(util.BuildAdminUrl("/department-postion/list")).SetAuth("yunj_department_list_postion_list"),
				}, actionCol.Options...)
			}
			cols = append(cols, actionCol)
			return
		}).
		Validator(func(t *builder.Table) (validate.ValidatorInterface, error) {
			v := &departmentvalidator.Department{}
			v.InitValidator(v)
			return v, nil
		}).
		Count(func(t *builder.Table, requestParam *builderdto.TableRequestParam) (res builderdto.TableCountResult, err error) {
			// 状态数量查询
			query := global.MySQL.Model(&model.AdminDepartment{})
			var filterFormParam dto.AdminDepartmentPageListFilterFormParam
			util.MapToStruct(requestParam.FilterData(), &filterFormParam)
			s.listBuilderFilterQuery(query, requestParam, &filterFormParam)
			err = query.Count(&res.Count).Error
			if err != nil {
				err = fmt.Errorf("数据量查询异常！%v", err)
				return
			}
			return
		}).
		Items(func(t *builder.Table, requestParam *builderdto.TableRequestParam) (res builderdto.TableItemsResult, err error) {
			query := global.MySQL.Select("*")
			var filterFormParam dto.AdminDepartmentPageListFilterFormParam
			util.MapToStruct(requestParam.FilterData(), &filterFormParam)
			s.listBuilderFilterQuery(query, requestParam, &filterFormParam)
			if s.listBuilderIsTree(requestParam.State) {
				query.Order("sort asc")
			} else {
				query.Order("updated_at desc")
			}
			var departmentItems []*model.AdminDepartment
			if err = query.Find(&departmentItems).Error; err != nil {
				err = fmt.Errorf("数据查询异常！%v", err)
				return
			}
			res.Items, res.ExpandTreeNode, err = s.listBuilderItemsHandle(departmentItems)
			return
		}).
		Event(func(t *builder.Table, requestParam *builderdto.TableRequestParam) (res builderdto.TableEventResult, err error) {
			handleEventRes, err := builderenum.HandleEvent(ctx, &model.AdminDepartment{}, requestParam.EventCode, requestParam.Pks, func(param *builderenum.EventDbParam, e *builderenum.Event, pks []any) (err error) {
				if e.Value == builderenum.TABLE_EVENT_TYPE_DELETED {
					param.Data["code"] = gorm.Expr("concat(`code`, '_del_', ?)", time.Now().In(time.Local).Format("20060102150405"))
				}
				return
			})
			if err != nil {
				return
			}
			if handleEventRes != nil {
				if handleEventRes.Errcode == errcodeEnum.SUCCESS {
					// 清理缓存
					err = data.DepartmentPrepareOrReset()
					if err != nil {
						global.Logger.WithField("requestParam", requestParam).WithError(err).Error("权限管理事件处理后，清理缓存失败！")
					}
				}
				res.ResponseJsonData = handleEventRes
				return
			}
			// 清理缓存
			if requestParam.EventCode == "clearCache" {
				err = data.DepartmentPrepareOrReset()
				if err != nil {
					err = fmt.Errorf("清理缓存失败！%v", err)
					return
				}
				res.Reload = true
				res.Message = "缓存清理成功"
				return
			}
			err = fmt.Errorf("异常事件[%v]", requestParam.EventCode)
			return
		})
}

type DepartmentMemberCount struct {
	DepartmentId uint64
	MemberCount  uint64
}

// 列表数据项处理
func (s *departmentPage) listBuilderItemsHandle(items []*model.AdminDepartment) (resItems []*dto.AdminDepartmentPageListItemResponse, expandTreeNode bool, err error) {
	expandTreeNode = true
	if len(items) <= 0 {
		return
	}
	ids := make([]uint64, 0, len(items))
	for _, item := range items {
		ids = append(ids, item.Id)
	}
	tablePrefix := global.Config.Database.TablePrefix
	// 岗位数据
	var departmentPostions []*model.AdminDepartmentPostion
	existsSubQuery := global.MySQL.Table(tablePrefix+"admin_postion p").Select("1").
		Where("dp.postion_id = p.id and p.state = ?", stateenum.NORMAL)
	dbErr := global.MySQL.Table(tablePrefix+"admin_department_postion dp").Select("dp.*").
		Where("department_id in ? and state = ? and exists (?)", ids, stateenum.NORMAL, existsSubQuery).Find(&departmentPostions).Error
	if dbErr != nil {
		err = fmt.Errorf("部门岗位数据获取失败！%v", dbErr)
		return
	}
	departmentPostionIds := map[uint64][]uint64{}
	for _, v := range departmentPostions {
		if _, exists := departmentPostionIds[v.DepartmentId]; !exists {
			departmentPostionIds[v.DepartmentId] = []uint64{}
		}
		departmentPostionIds[v.DepartmentId] = append(departmentPostionIds[v.DepartmentId], v.PostionId)
	}
	// 成员数
	var departmentMemberCounts []*DepartmentMemberCount
	err = global.MySQL.Table(tablePrefix+"admin_member_department mr").Where(
		"mr.department_id in ? and exists (?)",
		ids,
		global.MySQL.Table(tablePrefix+"admin_member m").Select("1").Where("m.id = mr.member_id and m.state = ?", stateenum.NORMAL),
	).Group("mr.department_id").Select("mr.department_id, count(mr.member_id) as member_count").Find(&departmentMemberCounts).Error
	if err != nil {
		err = fmt.Errorf("角色成员数据查询异常！%v", err)
		return
	}
	memberCounts := map[uint64]uint64{}
	for _, departmentMemberCount := range departmentMemberCounts {
		memberCounts[departmentMemberCount.DepartmentId] = departmentMemberCount.MemberCount
	}

	resItems = make([]*dto.AdminDepartmentPageListItemResponse, 0, len(items))
	for _, item := range items {
		resItem := &dto.AdminDepartmentPageListItemResponse{
			Id:    item.Id,
			Pid:   item.Pid,
			Type:  item.Type,
			Name:  item.Name,
			Code:  item.Code,
			Intro: item.Intro,
		}
		// 岗位数据
		postionIds := []uint64{}
		if postionIdsVal, exists := departmentPostionIds[item.Id]; exists {
			postionIds = postionIdsVal
		}
		resItem.PostionIds = postionIds
		// member_count
		if memberCount, exists := memberCounts[item.Id]; exists {
			resItem.MemberCount = memberCount
		}
		resItems = append(resItems, resItem)
	}
	return
}

// 列表过滤条件查询对象
func (s *departmentPage) listBuilderFilterQuery(query *gorm.DB, requestParam *builderdto.TableRequestParam, filterFormParam *dto.AdminDepartmentPageListFilterFormParam) {
	// ids
	if len(requestParam.Pks) > 0 {
		query.Where("`id` in ?", requestParam.Pks)
	}
	// state
	stateWhere := stateenum.NORMAL
	if stateCode, ok := stateenum.IsValue(requestParam.State); ok {
		stateWhere = stateCode
	}
	query.Where("state = ?", stateWhere)
	// keywords
	if filterFormParam.Keywords != "" {
		keywordsWhere := "%" + filterFormParam.Keywords + "%"
		query.Where("`name` like ? or code like ?", keywordsWhere, keywordsWhere)
	}
}

// 表单构建器
func (s *departmentPage) FormBuilder(ctx *gin.Context, isEdit, isAddChild bool) (formBuilder *builder.Form) {
	formBuilder = builder.NewForm(ctx, "DepartmentForm").
		Field(func(f *builder.Form, tab *builderdto.FormTab) (fields []form.FieldInterface, err error) {
			isAddChildDefault := uint8(0)
			if isAddChild {
				isAddChildDefault = uint8(1)
			}
			fields = []form.FieldInterface{
				field.NewHidden("is_add_child").SetVerify("in:0,1").SetDefault(isAddChildDefault),
				field.NewDropdownSearch("pid", "父级").SetReadonly(isAddChild).SetDesc("不选择则为顶级部门").SetOptionsApi(util.BuildAdminUrl("/department/pidDropdownSearchOptions")),
				field.NewRadio("type", "类型").SetOptions(builder.GetEnumOptions(departmentenum.TypeEnum)),
				field.NewText("name", "名称").SetVerify("required|max:100"),
				field.NewText("code", "标识").SetVerify("max:50").SetDesc("全局唯一"),
				field.NewTextarea("intro", "简介").SetVerify("max:200"),
			}
			if isEdit {
				fields = append(fields, field.NewHidden("id"))
			}
			// 设置栅格布局
			for _, v := range fields {
				v.SetFieldGrid([]any{12, 6, "6 l3 r3"})
			}
			return
		}).
		Button(func(f *builder.Form) (buttons []form.ButtonInterface, err error) {
			if isEdit {
				buttons = []form.ButtonInterface{
					button.NewReset(),
					button.NewSubmit(),
				}
			} else {
				buttons = []form.ButtonInterface{
					button.NewClear(),
					button.NewSubmit(),
				}
			}
			return
		}).
		Validator(func(f *builder.Form) (validate.ValidatorInterface, error) {
			v := &departmentvalidator.Department{}
			v.InitValidator(v)
			return v, nil
		}).
		Submit(func(f *builder.Form, values map[string]any) (res builderdto.FormSubmitResult, err error) {
			var dbDepartment *model.AdminDepartment
			dbDepartment = util.MapKeyValue(values, "dbDepartment", dbDepartment)
			if dbDepartment.Id > 0 {
				// 修改
				err = global.MySQL.Select("pid", "type", "name", "code", "intro").Save(dbDepartment).Error
				if err != nil {
					err = fmt.Errorf("编辑失败！%v", err.Error())
					return
				}
			} else {
				// 新增
				err = global.MySQL.Create(dbDepartment).Error
				if err != nil {
					err = fmt.Errorf("新增失败！%v", err.Error())
					return
				}
			}
			// 处理所有部门数据的完整属性值，并清理缓存
			err = data.HandleAllDepartmentFullAttr()
			if err != nil {
				return
			}
			res.Reload = true
			return
		})

	if isEdit || isAddChild {
		formBuilder.Load(func(f *builder.Form) (res any, err error) {
			id := util.QueryUint64Param(ctx, "id")
			if id <= 0 {
				err = fmt.Errorf("异常数据")
				return
			}
			if isAddChild {
				res = dto.AdminDepartmentPageFormLoadResponse{
					Pid: id,
				}
			} else {
				var loadRes dto.AdminDepartmentPageFormLoadResponse
				dbErr := global.MySQL.Model(&model.AdminDepartment{}).Where("`id` = ? and state <> ?", id, stateenum.DELETED).First(&loadRes).Error
				if (dbErr != nil && !errors.Is(dbErr, gorm.ErrRecordNotFound)) || loadRes.Id <= 0 {
					err = fmt.Errorf("数据获取异常！%v", dbErr)
					return
				}
				res = loadRes
			}
			return
		})
	}
	return
}

// 父级下拉搜索选项
func (s *departmentPage) PIdDropdownSearchOptions(ctx *gin.Context) (options []builderdto.Option, err error) {
	param := util.QueryParams(ctx)
	query := global.MySQL.Where("state = ?", stateenum.NORMAL).Limit(20).Select("`id`,name,code")
	if values, exists := param["values"]; exists {
		if values, ok := values.(string); ok && values != "" {
			if ids := util.ToUint64Slice(strings.Split(values, ",")); len(ids) > 0 {
				query = query.Where("`id` in ?", ids)
			}
		}
	}
	if keywords, exists := param["keywords"]; exists {
		if keywords, ok := keywords.(string); ok && keywords != "" {
			likeKeywords := "%" + keywords + "%"
			query = query.Where("(`name` like ? or `code` like ?)", likeKeywords, likeKeywords)
		}
	}
	var items []model.AdminDepartment
	dbErr := query.Find(&items).Error
	if dbErr != nil {
		err = fmt.Errorf("数据查询异常！%v", dbErr)
		return
	}
	options = make([]builderdto.Option, 0, len(items))
	for _, item := range items {
		txt := item.Name
		if item.Code != "" {
			txt += fmt.Sprintf("(%s)", item.Code)
		}
		options = append(options, builderdto.Option{Value: item.Id, Label: txt})
	}
	return
}

// 下拉搜索选项
func (s *departmentPage) DropdownSearchOptions(ctx *gin.Context) (options []builderdto.Option, err error) {
	param := util.QueryParams(ctx)
	query := global.MySQL.Where("state = ?", stateenum.NORMAL).Limit(20).Select("`id`,`type`,name,code")
	if values, exists := param["values"]; exists {
		if values, ok := values.(string); ok && values != "" {
			if ids := util.ToUint64Slice(strings.Split(values, ",")); len(ids) > 0 {
				query = query.Where("`id` in ?", ids)
			}
		}
	}
	if keywords, exists := param["keywords"]; exists {
		if keywords, ok := keywords.(string); ok && keywords != "" {
			likeKeywords := "%" + keywords + "%"
			query = query.Where("(`name` like ? or `code` like ?)", likeKeywords, likeKeywords)
		}
	}
	var items []model.AdminDepartment
	dbErr := query.Find(&items).Error
	if dbErr != nil {
		err = fmt.Errorf("数据查询异常！%v", dbErr)
		return
	}
	options = make([]builderdto.Option, 0, len(items))
	for _, item := range items {
		txt := fmt.Sprintf("【%s】%s", departmentenum.TypeByValue(item.Type).GetDesc(), item.Name)
		if item.Code != "" {
			txt += fmt.Sprintf("(%s)", item.Code)
		}
		options = append(options, builderdto.Option{Value: item.Id, Label: txt})
	}
	return
}
